﻿using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;

namespace HYGE.AsyncCommand
{
    /// <summary>
    /// Func{TResult}デリゲートを使用するIAsyncJob実装。
    /// onCompleted,onErrorデリゲートはUIスレッドで実行される。
    /// </summary>
    internal class AsyncFuncJob<TResult> : AsyncJobBase
    {
        #region Fields

        #region task
        /// <summary>
        /// コンストラクタで指定されたデリゲートから生成されるTask。
        /// </summary>
        private Task<TResult> task = null;
        #endregion

        #endregion

        #region Constructors

        #region Default
        /// <summary>
        /// デフォルトコンストラクタ。
        /// </summary>
        /// <param name="executeAsync"></param>
        /// <param name="onCompleted"></param>
        /// <param name="onError"></param>
        /// <param name="desciption"></param>
        public AsyncFuncJob(Func<TResult> executeAsync, Action<int, TResult> onCompleted, Action<int, AggregateException> onError = null, string desciption = "")
            : base(desciption)
        {
            //引数チェック
            if (executeAsync == null)
                throw (new ArgumentNullException("executeAsync"));
            if (onCompleted == null)
                throw (new ArgumentNullException("onCompleted"));

            //キャンセル操作は非サポート
            this.CanBeCanceled = false;

            //task生成
            this.task = new Task<TResult>(() =>
                {
                    this.IsRunning = true;
                    //非同期実行
                    return (executeAsync());
                }
            );
            this.task.ContinueWith(completedTask =>
                {
                    //状態に応じて処理変更
                    if (completedTask.IsFaulted)
                    {
                        //例外発生時
                        if (onError != null)
                            onError(this.Id,completedTask.Exception);
                        this.IsFaulted = completedTask.IsFaulted;
                        this.Exception = completedTask.Exception;
                    }
                    else
                        onCompleted(this.Id, completedTask.Result);

                    //終了処理
                    this.task.Dispose();
                    this.task = null;
                    this.IsRunning = false;
                    this.IsCompleted = true;
                },
                TaskScheduler.FromCurrentSynchronizationContext()
            );
            this.Id = this.task.Id;
        }
        #endregion

        #endregion

        #region OverrideMethods

        #region ReleaseManagedResources
        /// <summary>
        /// マネージリソース開放を行う。
        /// </summary>
        protected override void ReleaseManagedResources()
        {
            if (!this.IsCompleted)
                throw (new InvalidOperationException(Properties.Resources.AsyncJob_Dispose_InvalidOperationExceptionMessage));

            if (this.task != null)
            {
                this.task.Dispose();
                this.task = null;
            }
        }
        #endregion

        #region Cancel
        /// <summary>
        /// Cancelのオーバーライド。
        /// </summary>
        public override void Cancel()
        {
            throw (new NotSupportedException());
        }
        #endregion

        #region Start
        /// <summary>
        /// Startのオーバーライド。
        /// </summary>
        /// <param name="scheduler"></param>
        public override void Start(TaskScheduler scheduler = null)
        {
            if (scheduler == null)
                this.task.Start();
            else
                this.task.Start(scheduler);
        }
        #endregion

        #endregion
    }

    /// <summary>
    /// Func{T,TResult}デリゲートを使用するIAsyncJob実装。
    /// onCompleted,onError,onCanceledデリゲートはUIスレッドで実行される。
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    internal class AsyncFuncJob<T, TResult> : AsyncJobBase
    {
        #region Fields

        #region task
        /// <summary>
        /// コンストラクタで指定されたデリゲートから生成されるTask。
        /// </summary>
        private Task<TResult> task = null;
        #endregion

        #region cancellationTokenSource
        /// <summary>
        /// キャンセル操作サポート用のCancellationTokenSource。
        /// TがICancelableCommandParameterを実装している場合に、コンストラクタ内で生成される。
        /// </summary>
        private CancellationTokenSource cancellationTokenSource = null;
        #endregion

        #endregion

        #region Constructors

        #region Default
        /// <summary>
        /// デフォルトコンストラクタ。
        /// </summary>
        /// <param name="executeAsync"></param>
        /// <param name="parameter"></param>
        /// <param name="onCompleted"></param>
        /// <param name="onError"></param>
        /// <param name="onCanceled"></param>
        /// <param name="desciption"></param>
        public AsyncFuncJob(Func<T, TResult> executeAsync, T parameter, Action<int, TResult> onCompleted, Action<int, AggregateException> onError = null, Action<int> onCanceled = null, string desciption = "")
            : base(desciption)
        {
            //引数チェック
            if (executeAsync == null)
                throw (new ArgumentNullException("executeAsync"));
            if (onCompleted == null)
                throw (new ArgumentNullException("onCompleted"));

            //キャンセル操作サポートを確認
            if (typeof(T).GetInterface(typeof(ICancelableAsyncJobParameter).Name) != null)
            {
                this.cancellationTokenSource = new CancellationTokenSource();
                this.CanBeCanceled = true;
                ICancelableAsyncJobParameter castedParameter = parameter as ICancelableAsyncJobParameter;
                if (castedParameter != null)
                    castedParameter.CancellationToken = this.cancellationTokenSource.Token;
            }

            //task生成
            Func<object, TResult> executeAsyncWrapper = new Func<object, TResult>((param) =>
                {
                    this.IsRunning = true;
                    //非同期実行
                    if (param == null)
                        return (executeAsync(default(T)));
                    else
                        return (executeAsync((T)param));
                }
            );
            this.task = this.CanBeCanceled ? new Task<TResult>(executeAsyncWrapper, parameter, this.cancellationTokenSource.Token) : new Task<TResult>(executeAsyncWrapper, parameter);
            this.task.ContinueWith(completedTask =>
                {
                    //状態に応じて処理変更
                    if (completedTask.IsFaulted)
                    {
                        //例外発生時
                        if (onError != null)
                            onError(this.Id, completedTask.Exception);
                        this.IsFaulted = completedTask.IsFaulted;
                        this.Exception = completedTask.Exception;
                    }
                    else if (completedTask.IsCanceled)
                    {
                        //キャンセル時
                        if (onCanceled != null)
                            onCanceled(this.Id);
                        this.IsCanceled = true;
                    }
                    else
                        onCompleted(this.Id, completedTask.Result);

                    //終了処理
                    this.task.Dispose();
                    this.task = null;
                    this.IsRunning = false;
                    this.IsCompleted = true;
                },
                TaskScheduler.FromCurrentSynchronizationContext()
            );
            this.Id = this.task.Id;
        }
        #endregion

        #endregion

        #region OverrideMethods

        #region ReleaseManagedResources
        /// <summary>
        /// マネージリソース開放を行う。
        /// </summary>
        protected override void ReleaseManagedResources()
        {
            if (!this.IsCompleted)
                throw (new InvalidOperationException(Properties.Resources.AsyncJob_Dispose_InvalidOperationExceptionMessage));

            if (this.CanBeCanceled)
            {
                this.cancellationTokenSource.Dispose();
                this.cancellationTokenSource = null;
            }

            if (this.task != null)
            {
                this.task.Dispose();
                this.task = null;
            }
        }
        #endregion

        #region Cancel
        /// <summary>
        /// Cancelのオーバーライド。
        /// </summary>
        public override void Cancel()
        {
            if (!this.CanBeCanceled)
                throw (new NotSupportedException());

            if (!this.IsCompleted)
                this.cancellationTokenSource.Cancel();
        }
        #endregion

        #region Start
        /// <summary>
        /// Startのオーバーライド。
        /// </summary>
        /// <param name="scheduler"></param>
        public override void Start(TaskScheduler scheduler = null)
        {
            if (scheduler == null)
                this.task.Start();
            else
                this.task.Start(scheduler);
        }
        #endregion

        #endregion
    }
}
